package io.netty.handler.codec;

import static io.netty.util.internal.ObjectUtil.checkNotNull;
import static io.netty.util.internal.ObjectUtil.checkPositive;
import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;

import java.nio.ByteOrder;
import java.util.List;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;

public class LengthFieldBasedFrameDecoder extends ByteToMessageDecoder {

    // 大小端排序的工具
    private final ByteOrder byteOrder;
    // 最大帧长度
    private final int maxFrameLength;
    // 长度域字段偏移位置
    private final int lengthFieldOffset;
    // 长度域字段本身长度
    private final int lengthFieldLength;
    // 根据长度域字段偏移位置 和 长度域自身长度 计算出来的 长度域 结束偏移位置
    private final int lengthFieldEndOffset;
    // 调整长度
    private final int lengthAdjustment;
    // 需要跳过的字节
    private final int initialBytesToStrip;
    // 快速失败..
    private final boolean failFast;

    // true 表示decoder 开启丢弃模式，false 正常工作模式
    private boolean discardingTooLongFrame;
    // 当某个帧的长度域内的长度值 超过 maxLength ，则开启丢弃模式，此字段记录需要丢弃的 帧长度。
    private long tooLongFrameLength;
    // 记录还剩余多少字节 需要丢弃。
    private long bytesToDiscard;

    public LengthFieldBasedFrameDecoder(
            int maxFrameLength,
            int lengthFieldOffset, int lengthFieldLength) {
        this(maxFrameLength, lengthFieldOffset, lengthFieldLength, 0, 0);
    }

    public LengthFieldBasedFrameDecoder(
            int maxFrameLength,
            int lengthFieldOffset, int lengthFieldLength,
            int lengthAdjustment, int initialBytesToStrip) {
        this(
                maxFrameLength,
                lengthFieldOffset, lengthFieldLength, lengthAdjustment,
                initialBytesToStrip, true);
    }

    public LengthFieldBasedFrameDecoder(
            int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
            int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
        this(
                ByteOrder.BIG_ENDIAN, maxFrameLength, lengthFieldOffset, lengthFieldLength,
                lengthAdjustment, initialBytesToStrip, failFast);
    }

    public LengthFieldBasedFrameDecoder(
            ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,
            int lengthAdjustment, int initialBytesToStrip, boolean failFast) {

        this.byteOrder = checkNotNull(byteOrder, "byteOrder");

        checkPositive(maxFrameLength, "maxFrameLength");

        checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");

        checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");

        if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {
            throw new IllegalArgumentException(
                    "maxFrameLength (" + maxFrameLength + ") " +
                    "must be equal to or greater than " +
                    "lengthFieldOffset (" + lengthFieldOffset + ") + " +
                    "lengthFieldLength (" + lengthFieldLength + ").");
        }

        this.maxFrameLength = maxFrameLength;
        this.lengthFieldOffset = lengthFieldOffset;
        this.lengthFieldLength = lengthFieldLength;
        this.lengthAdjustment = lengthAdjustment;
        this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;
        this.initialBytesToStrip = initialBytesToStrip;
        this.failFast = failFast;
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = decode(ctx, in);
        if (decoded != null) {
            out.add(decoded);
        }
    }

    private void discardingTooLongFrame(ByteBuf in) {
        //待丢弃字节长度
        long bytesToDiscard = this.bytesToDiscard;
        //求出 socket 底层获取的数据 和 待丢弃字节长度的最小值
        int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
        //跳过
        in.skipBytes(localBytesToDiscard);

        bytesToDiscard -= localBytesToDiscard;
        // 更新待丢弃的字节长度..
        this.bytesToDiscard = bytesToDiscard;

        failIfNecessary(false);
    }

    private static void failOnNegativeLengthField(ByteBuf in, long frameLength, int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
           "negative pre-adjustment length field: " + frameLength);
    }

    private static void failOnFrameLengthLessThanLengthFieldEndOffset(ByteBuf in,
                                                                      long frameLength,
                                                                      int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
           "Adjusted frame length (" + frameLength + ") is less " +
              "than lengthFieldEndOffset: " + lengthFieldEndOffset);
    }

    // 参数1：堆积区
    // 参数2：当前数据帧长度值
    private void exceededFrameLength(ByteBuf in, long frameLength) {
        // 当前帧长度 - 堆积区可读数据量大小
        long discard = frameLength - in.readableBytes();

        tooLongFrameLength = frameLength;


        // 条件成立：说明 堆积区 内的数据 是 > frameLength 大小的，可以直接将本帧数据 全部跳过。
        if (discard < 0) {
            // buffer contains more bytes then the frameLength so we can discard all now
            in.skipBytes((int) frameLength);
        } else {
            // 执行到这？ 说明堆积区内的数据是 当前帧的 一部分 数据.. 这种情况需要开启 丢弃模式！
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            // 当前帧 还有多少字节 待丢弃..
            bytesToDiscard = discard;
            // 直接将 堆积区 数据全部丢掉，因为 堆积区 是当前帧的一部分数据..
            in.skipBytes(in.readableBytes());
        }

        failIfNecessary(true);
    }

    private static void failOnFrameLengthLessThanInitialBytesToStrip(ByteBuf in,
                                                                     long frameLength,
                                                                     int initialBytesToStrip) {
        in.skipBytes((int) frameLength);
        throw new CorruptedFrameException(
           "Adjusted frame length (" + frameLength + ") is less " +
              "than initialBytesToStrip: " + initialBytesToStrip);
    }

    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        // 条件成立：说明是丢弃模式..
        if (discardingTooLongFrame) {
            discardingTooLongFrame(in);
        }
        //半包数据...
        // 条件成立：说明堆积区 内的数据 连 长度域 都没加载完.. 此时什么也无法解析，返回null
        if (in.readableBytes() < lengthFieldEndOffset) {
            return null;
        }


        // 计算出 真实的 长度域开始位置
        int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;

        // 获取frame的长度域的值，这个长度域的值 不包含 lengthAdjustment 调整值。
        // 参数1：堆积区
        // 参数2：真实的 长度域开始位置
        // 参数3：长度域的长度
        // 参数4：大小端排序工具对象
        long frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

        if (frameLength < 0) {
            failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
        }


        // 计算出来的 这个 frameLength 是整个包的长度..别管包内有多少个 字段域。
        frameLength += lengthAdjustment + lengthFieldEndOffset;


        if (frameLength < lengthFieldEndOffset) {
            failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
        }

        // 条件成立：说明 当前帧 的长度 太长了，超过你们制定的协议规格了.. 怎么办？丢！
        if (frameLength > maxFrameLength) {
            // 参数1：堆积区
            // 参数2：当前数据帧长度值
            exceededFrameLength(in, frameLength);
            return null;
        }

        // never overflows because it's less than maxFrameLength
        // 帧大小
        int frameLengthInt = (int) frameLength;

        // 条件成立：说明当前 堆积区 是当前帧的 半包数据，需要等待 下半部分数据 累积进去之后 再解码...
        if (in.readableBytes() < frameLengthInt) {
            // 返回null ，表示未解析成功..
            return null;
        }

        // 执行到这里，说明堆积区 数据量 >= 帧大小的..

        if (initialBytesToStrip > frameLengthInt) {
            failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
        }

        // 跳过一部分 字节..
        in.skipBytes(initialBytesToStrip);

        // extract frame
        int readerIndex = in.readerIndex();

        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        // 提取 真实的帧数据
        ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
        // 更新 堆积区 readerIndex
        in.readerIndex(readerIndex + actualFrameLength);
        return frame;
    }

    // 参数1：堆积区
    // 参数2：真实的 长度域开始位置
    // 参数3：长度域的长度
    // 参数4：大小端排序工具对象
    protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
        buf = buf.order(order);

        // 帧长度
        long frameLength;

        switch (length) {
        case 1:
            frameLength = buf.getUnsignedByte(offset);
            break;
        case 2:
            frameLength = buf.getUnsignedShort(offset);
            break;
        case 3:
            frameLength = buf.getUnsignedMedium(offset);
            break;
        case 4:
            frameLength = buf.getUnsignedInt(offset);
            break;
        case 8:
            frameLength = buf.getLong(offset);
            break;
        default:
            throw new DecoderException(
                    "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
        }
        return frameLength;
    }

    private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
        // 条件成立：说明 当前帧 所以数据 都已经跳过了，可以关闭 丢弃模式了。
        if (bytesToDiscard == 0) {

            // Reset to the initial state and tell the handlers that
            // the frame was too large.
            long tooLongFrameLength = this.tooLongFrameLength;
            // 设置需要丢弃的帧长度为0
            this.tooLongFrameLength = 0;
            // 关闭丢弃模式
            discardingTooLongFrame = false;

            if (!failFast || firstDetectionOfTooLongFrame) {
                fail(tooLongFrameLength);
            }

        } else {
            // 执行到这里，说明当前帧 还有数据待丢弃..
            // Keep discarding and notify handlers if necessary.
            if (failFast && firstDetectionOfTooLongFrame) {
                fail(tooLongFrameLength);
            }
        }

    }

    protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.retainedSlice(index, length);
    }

    private void fail(long frameLength) {
        if (frameLength > 0) {
            throw new TooLongFrameException(
                            "Adjusted frame length exceeds " + maxFrameLength +
                            ": " + frameLength + " - discarded");
        } else {
            throw new TooLongFrameException(
                            "Adjusted frame length exceeds " + maxFrameLength +
                            " - discarding");
        }
    }
}
